//
//  LBFaceSkinDetect.mm
//  OpenGLGame
//
//  Created by Nguyen Cong Huy on 2/27/13.
//  Copyright (c) 2013 UIT. All rights reserved.
//

#include "LBFaceSkinDetect.h"
#include "LBUtils.h"
#include "LBGraphics.h"

char faceSkinFilterFragStr[] = ""

"precision mediump float;"

"varying lowp vec2 vtexCoor;"
"uniform sampler2D texture;"
"uniform int numFace;"
"uniform vec4 averageSkinColor;"
"uniform float faceBoundData[100];"             //MAX_NUM_FACE = 10


"bool isYellowSkinColor(vec4 color){"

"   vec4 highSkinColor = vec4(1.0, 1.0, 1.0, 1.0);"
"   vec4 lowSkinColor = vec4(0.373, 0.157, 0.078, 1.0);"

"   if(numFace > 0){"
"       lowSkinColor = averageSkinColor*0.3;"
"       highSkinColor = averageSkinColor*1.5;"
"   }"

"   const lowp float lowRedPercent = 0.3;"
"   const lowp float highRedPercent = 0.6;"
"   const lowp float lowGreenPercent = 0.2;"
"   const lowp float highGreenPercent = 0.35;"
"   const lowp float lowBluePercent = 0.1;"
"   const lowp float highBluePercent = 0.35;"

"   const float lowRedBlueRatio = 1.0;"
"   const float highRedBlueRatio = 4.6;"
"   const float lowGreenBlueRatio = 0.9;"
"   const float highGreenBlueRatio = 2.3;"
"   const float lowRedGreenRatio = 1.0;"
"   const float highRedGreenRatio = 2.3;"

"   const float lowSumColor = 0.8;"
"   const float highSumColor = 2.9;"

"   float redPercent = color.r / (color.r + color.g + color.b);"
"   float greenPercent = color.g / (color.r + color.g + color.b);"
"   float bluePercent = color.b / (color.r + color.g + color.b);"

"   float redBlueRatio = color.r/color.b;"
"   float greenBlueRatio = color.g/color.b;"
"   float redGreenRatio = color.r/color.g;"

"   float sumColor = color.r + color.g + color.b;"

"   float maxRGB = max(max(color.r, color.g), color.b);"
"   float minRGB = min(min(color.r, color.g), color.b);"

"   if("
"          ((color.r > lowSkinColor.r && color.r < highSkinColor.r"
"       && color.g > lowSkinColor.g && color.g < highSkinColor.g"
"       && color.b > lowSkinColor.b && color.b < highSkinColor.b && numFace < 1)"
"       || (distance(averageSkinColor, color) < 0.5 && numFace > 0))"


//      percent color
"       && redPercent > lowRedPercent && redPercent < highRedPercent"
"       && greenPercent > lowGreenPercent && greenPercent < highGreenPercent"
"       && bluePercent > lowBluePercent && bluePercent < highBluePercent"

//      ratio
"       && redBlueRatio > lowRedBlueRatio && redBlueRatio < highRedBlueRatio"
"       && greenBlueRatio > lowGreenBlueRatio && greenBlueRatio < highGreenBlueRatio"
"       && redGreenRatio > lowRedGreenRatio && redGreenRatio < highRedGreenRatio"

//      sum color
"       && sumColor > lowSumColor &&  sumColor < highSumColor"

//http://www.paulmckevitt.com/pubs/abbassp(skintone)paper.pdf
"       && abs(color.r - color.g) > 0.059 && maxRGB - minRGB > 0.059"

"   ){"
"       return true;"
"   }"
"   return false;"
"}"

"bool isInFace(){"
"   for(int i = 0; i < numFace*4; i = i+10){"
//"       float faceWidth = faceBoundData[i + 2];"
//"       float faceHeight = faceBoundData[i + 3];"
//"       float topAdd = faceHeight*0.2;"
//"       float bottomAdd = faceHeight*0.1;"
//"       float topAdd = 0.0;"
//"       float bottomAdd = 0.0;"

//"       float left = faceBoundData[i];"
//"       float bottom = faceBoundData[i + 1] - bottomAdd;"
//"       float right = left + faceBoundData[i + 2];"
//"       float top = bottom + faceBoundData[i + 3] + topAdd  + bottomAdd;"
//"       if(vtexCoor.x > left && vtexCoor.x < right && vtexCoor.y > bottom && vtexCoor.y <  top) return true;"
//"   }"
"       float ax = faceBoundData[i+2]/2.0;"
"       float ay = faceBoundData[i+3]/2.0;"
"       vec2 center = vec2(faceBoundData[i] + ax, faceBoundData[i + 1] + ay);"
     
"       vec2 leftEye = vec2(faceBoundData[i+4], faceBoundData[i+5]);"
"       vec2 rightEye = vec2(faceBoundData[i+6], faceBoundData[i+7]);"
"       vec2 mouth = vec2(faceBoundData[i+8], faceBoundData[i+9]);"
"       float eyeAx = ax/4.0;"
"       float eyeAy = ay/5.0;"
"       float mouthAx = ax/2.25;"
"       float mouthAy = ay/3.5;"

"       ax *= 1.2;"
"       ay *= 1.4;"
"       if("
"           (vtexCoor.x - center.x)*(vtexCoor.x - center.x) / (ax*ax) + (vtexCoor.y - center.y)*(vtexCoor.y - center.y)/ (ay*ay) - 1.0 < 0.0"
"           && (vtexCoor.x - leftEye.x)*(vtexCoor.x - leftEye.x) / (eyeAx*eyeAx)+ (vtexCoor.y - leftEye.y)*(vtexCoor.y - leftEye.y)/ (eyeAy*eyeAy) - 1.0 > 0.0"
"           && (vtexCoor.x - rightEye.x)*(vtexCoor.x - rightEye.x) / (eyeAx*eyeAx) + (vtexCoor.y - rightEye.y)*(vtexCoor.y - rightEye.y)/ (eyeAy*eyeAy) - 1.0 > 0.0"
"           && (vtexCoor.x - mouth.x)*(vtexCoor.x - mouth.x) / (mouthAx*mouthAx)+ (vtexCoor.y - mouth.y)*(vtexCoor.y - mouth.y)/(mouthAy*mouthAy) - 1.0 > 0.0"
"       ) return true;"
"   }"
"   return false;"
"}"

"bool isInEyeAndMouth(){"
"   for(int i = 0; i < numFace*4; i = i+10){"

"       float ax = faceBoundData[i+2]/2.0;"
"       float ay = faceBoundData[i+3]/2.0;"

"       vec2 leftEye = vec2(faceBoundData[i+4], faceBoundData[i+5]);"
"       vec2 rightEye = vec2(faceBoundData[i+6], faceBoundData[i+7]);"
"       vec2 mouth = vec2(faceBoundData[i+8], faceBoundData[i+9]);"
"       float eyeAx = ax/4.0;"
"       float eyeAy = ay/5.0;"
"       float mouthAx = ax/2.25;"
"       float mouthAy = ay/3.5;"
"       if("
"           (vtexCoor.x - leftEye.x)*(vtexCoor.x - leftEye.x) / (eyeAx*eyeAx)+ (vtexCoor.y - leftEye.y)*(vtexCoor.y - leftEye.y)/ (eyeAy*eyeAy) - 1.0 < 0.0"
"           || (vtexCoor.x - rightEye.x)*(vtexCoor.x - rightEye.x) / (eyeAx*eyeAx) + (vtexCoor.y - rightEye.y)*(vtexCoor.y - rightEye.y)/ (eyeAy*eyeAy) - 1.0 < 0.0"
"           || (vtexCoor.x - mouth.x)*(vtexCoor.x - mouth.x) / (mouthAx*mouthAx)+ (vtexCoor.y - mouth.y)*(vtexCoor.y - mouth.y)/(mouthAy*mouthAy) - 1.0 < 0.0"
"       ) return true;"
"   }"
"   return false;"
"}"

"void main()"
"{"
"   lowp vec4 textureColor = texture2D(texture, vtexCoor);"

"   gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);"

//"   if(isYellowSkinColor(textureColor)){"
//"       if(numFace < 1){"
//"           gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);"          // TODO: process skin
//"       }"
//"       else{"
//"           if(isInFace()) gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);"    // TODO: process skin
//"       }"
//"   }"

"   if(isYellowSkinColor(textureColor)) gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);"
//"    if(isInFace()) gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);"
//"   if(isYellowSkinColor(textureColor) && isInEyeAndMouth() == false) gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);"

"}";

LBFaceSkinDetect::LBFaceSkinDetect(){
    init(0, faceSkinFilterFragStr);
    faceBoundData = 0;
    LBGraphics::getInstance()->useProgram(program);
    numFaceUniform = program->uniformIndex("numFace");
    faceBoundDataUniform = program->uniformIndex("faceBoundData");
    averageSkinColorUniform = program->uniformIndex("averageSkinColor");
}

void LBFaceSkinDetect::setFaceBoundData(int numFace, float* faceBoundData){
    this->numFace = numFace;
    if(faceBoundData != 0){
        this->faceBoundData = new float[numFace*NUM_DATA_IN_A_FACE];
        memcpy(this->faceBoundData, faceBoundData, sizeof(float)*numFace*NUM_DATA_IN_A_FACE);
    }
    
    LBGraphics::getInstance()->useProgram(program);
    glUniform1i(numFaceUniform, numFace);
    if(faceBoundData != 0) glUniform1fv(faceBoundDataUniform, numFace*NUM_DATA_IN_A_FACE, this->faceBoundData);
}

LBFaceSkinDetect::~LBFaceSkinDetect(){
    DELETE_SAFELY_ARRAY(faceBoundData);
}

int LBFaceSkinDetect::getNumFace(){
    return numFace;
}

float* LBFaceSkinDetect::getAverageSkinColor(){
    return averageSkinColor;
}

float* LBFaceSkinDetect::getFaceBoundData(){
    return faceBoundData;
}

void LBFaceSkinDetect::setAverageSkinColor(float red, float green, float blue){
    averageSkinColor[0] = red;
    averageSkinColor[1] = green;
    averageSkinColor[2] = blue;
    averageSkinColor[3] = 1.0f;
    LBGraphics::getInstance()->useProgram(program);
    glUniform4f(averageSkinColorUniform, red, green, blue, 1.0);
}